@tool
extends Node3D

class_name ProceduralBuilding

@export_tool_button("Generate building") var btn_generate = generate_building
@export_tool_button("Clear") var btn_clear = clear
@export_tool_button("Generate slots") var btn_slots = prepare_flat_slots


@export var blueprint: BuildingBlueprint
@export var num_sections: int = 4
@export var num_floors: int = 5
@export var num_underground_layers: int = 1

@export var blocks_override: Dictionary

const BuildingBlock = preload("res://resources/procedural_buildings/building_block.gd")
const BuildingSection = preload("res://resources/procedural_buildings/building_section.gd")

const ROTATION_BY_ANCHOR = {
	'anchor_corner_left': 90,
	'anchor_corner_right': -90
}

@onready var procedural_building: Node3D = $"."

func generate_building():
	clear()
	build_blueprint(blueprint)

func prepare_flat_slots():
	for floor in num_floors:
		var floor_key = 'floor_%s' % floor
		blocks_override[floor_key] = {}
		for section in num_sections:
			var section_key = 'section_%s' % section
			blocks_override[floor_key][section_key] = {}
			for flat in range(10):
				var flat_key = 'anchor_flat_%s' % flat
				blocks_override[floor_key][section_key][flat_key] = []

func instantiate_block(
		block: BuildingBlock,
		block_additional_params: Dictionary,
		parent: Node3D = procedural_building,
	) -> Node3D:
	var scene = block.scene.instantiate()
	
	parent.add_child(scene)
	scene.owner = get_tree().edited_scene_root
	
	if block.childern:
		var anchor_keys = block.childern.keys()
		for anchor_key in anchor_keys:
			var anchor
			if anchor_key != 'root':
				anchor = scene.get_node(anchor_key)
			else:
				anchor = parent
			
			if block.childern != null:
				var children = block.childern[anchor_key]
				#var override_children = blocks_override['section_%s' % block_additional_params['section_number']]
				if (
					block_additional_params.has('section_number') and
					block_additional_params.has('floor_number') and 
					block_additional_params['floor_number'] >= 0
				):
					var floor_num = block_additional_params['floor_number']
					var section_num = block_additional_params['section_number']
					var override_children = blocks_override['floor_%s' % floor_num]['section_%s' % section_num] 
					if override_children.has(anchor_key) and not override_children[anchor_key].is_empty():
						print('Found override for %s. Floor %s Section %s' % [anchor_key, floor_num, section_num])
						print(children)
						children = override_children[anchor_key]
				
				for child in children:
					if child != null:
						if (
							child.metadata 
							and child.metadata.has('ignore_top_floor') 
							and child.metadata['ignore_top_floor']
							and block_additional_params
							and block_additional_params.has('floor_number')
							and block_additional_params.get('floor_number') == num_floors - 1
							):
							continue
						var subscene = instantiate_block(child, block_additional_params, scene)
						subscene.global_position = anchor.global_position
	return scene

func create_subfolder(name: String, save_to: Node3D = procedural_building) -> Node3D:
	var subfolder = Node3D.new()
	subfolder.name = name
	save_to.add_child(subfolder)
	subfolder.owner = get_tree().edited_scene_root
	return subfolder

func build_blueprint(blueprint: BuildingBlueprint):
	print('Constructing building of configuration: %s sections, %s floors, %s bottom skirt' % [num_sections, num_floors, num_underground_layers])
	var sections = []
	if num_sections == 1:
		sections = ['single']
	else:
		for i in range(num_sections):
			if i == 0:
				sections.append('left')
			elif i == num_sections - 1:
				sections.append('right')
			else:
				sections.append('middle')
	print('Sections configuration: %s' % [sections])
	# underground
	var last_root: Node3D = procedural_building
	var underground_folder = create_subfolder('underground')
	for index in range(num_underground_layers):
		var building_sections: Array[BuildingSection] = []
		for section in sections:
			building_sections.append(blueprint.section['%s_base_underground' % section])
		last_root = instatntiate_floor(building_sections, last_root, -1, underground_folder)
	# base
	var floor_folder = create_subfolder('floor_0')
	var building_sections: Array[BuildingSection] = []
	for section in sections:
		building_sections.append(blueprint.section['%s_base' % section])
	last_root = instatntiate_floor(building_sections, last_root, -1, floor_folder)
	# floors
	for floor in num_floors:
		building_sections = []
		if floor == 0:
			for section in sections:
				building_sections.append(blueprint.section['%s_window' % section])
		else:
			floor_folder = create_subfolder('floor_%s' % floor)
			print(floor_folder.name)
			for section in sections:
				building_sections.append(blueprint.section['%s_balcony' % section])
		last_root = instatntiate_floor(building_sections, last_root, floor, floor_folder)
	# roof
	var roof_folder = create_subfolder('roof')
	building_sections = []
	for section in sections:
		building_sections.append(blueprint.section['%s_roof' % section])
	last_root = instatntiate_floor(building_sections, last_root, -1, roof_folder)

func instatntiate_floor(
		sections: Array[BuildingSection], 
		root: Node3D = procedural_building, 
		floor_number = -1,
		save_to: Node3D = procedural_building
	) -> Node3D:
	var new_root: Node3D
	var previous_part: Node3D
	var back_pass = []
	var back_pass_anchors = []
	var block_additional_params = {
		'floor_number': floor_number,
		'section_number': 0
	}
	for section_index in sections.size():
		block_additional_params['section_number'] = section_index
		var section_save_to_name = 'section_%s' % section_index
		var section_save_to: Node3D
		if save_to.has_node(section_save_to_name):
			section_save_to = save_to.get_node(section_save_to_name)
		else:
			section_save_to = create_subfolder(section_save_to_name, save_to)
		print('Construction scetion %s at %s' % [section_index, save_to.name])
		var section = sections[section_index]
		for part_index in range(section.forward_pass.size()):
			var part = section.forward_pass[part_index]
			var part_scene = instantiate_block(part, block_additional_params, section_save_to)
			var part_anchor = section.forward_pass_anchors[part_index]
			if part_anchor == 'root':
				if part_scene.has_node('anchor_top'):
					new_root = part_scene.get_node('anchor_top')
				else:
					new_root = part_scene
				part_scene.global_position = root.global_position
			else:
				if section.internal_block and part_scene.has_node('anchor_staircase'):
					var internal_addons = section.internal_block
					var internals_block = BuildingBlock.new()
					internals_block.scene = internal_addons.block.scene
					
					for key in internal_addons.block.childern.keys():
						var concat_children = []
						concat_children.append_array(internal_addons.block.childern[key])
						if internal_addons.childern.has(key):
							concat_children.append_array(internal_addons.childern[key])
						internals_block.childern[key] = concat_children
					
					var internals_instance = instantiate_block(internals_block, block_additional_params, part_scene)
					internals_instance.position = part_scene.get_node('anchor_staircase').position
				part_scene.global_position = previous_part.get_node(part_anchor).global_position
				part_scene.rotation.y += previous_part.rotation.y
				if ROTATION_BY_ANCHOR.has(part_anchor):
					part_scene.rotation.y += deg_to_rad(ROTATION_BY_ANCHOR[part_anchor])
			previous_part = part_scene
		back_pass.push_front(section.backward_pass)
		back_pass_anchors.push_front(section.backward_pass_anchors)
	
	for section_backward_index in back_pass.size():
		var section_index = back_pass.size() - 1 - section_backward_index
		var section_save_to_name = 'section_%s' % section_index
		var section_save_to = save_to.get_node(section_save_to_name)
		block_additional_params['section_number'] = section_index
		for part_index in range(back_pass[section_backward_index].size()):
			var part = back_pass[section_backward_index][part_index]
			var part_scene = instantiate_block(part, block_additional_params, section_save_to)
			var part_anchor = back_pass_anchors[section_backward_index][part_index]
			
			part_scene.global_position = previous_part.get_node(part_anchor).global_position
			part_scene.rotation.y += previous_part.rotation.y
			if ROTATION_BY_ANCHOR.has(part_anchor):
				part_scene.rotation.y += deg_to_rad(ROTATION_BY_ANCHOR[part_anchor])
			previous_part = part_scene
	return new_root

func clear():
	for child in procedural_building.get_children():
		child.queue_free()
